﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.Kinect.Nui;
using System.Threading;
using System.IO;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using MocKinect.Proxies;
using System.Windows.Forms;

namespace MocKinect
{

    public class MocKinectDeviceCollection : List<Runtime>
    {
        public Runtime this[string instanceName]
        {
            get
            {
                return this.FirstOrDefault(k => k.InstanceName == instanceName);
            }
        }

        public event EventHandler<StatusChangedEventArgs> StatusChanged;

        internal void OnStatusChanged(object sender, StatusChangedEventArgs e)
        {
            if (StatusChanged != null)
                StatusChanged(sender, e);
        }
    }

    public class StatusChangedEventArgs : EventArgs
    {
        public Runtime KinectRuntime { get; internal set; }
        public KinectStatus Status { get; internal set; }
    }

    public class Runtime
    {
        #region Static members

        public static MocKinectDeviceCollection _kinects = null;
        public static MocKinectDeviceCollection Kinects
        {
            get
            {
                InitializeOnce();
                return _kinects;
            }
        }

        private static void InitializeOnce()
        {
            if (_kinects != null) return;
            Microsoft.Research.Kinect.Nui.Runtime.Kinects.StatusChanged += new EventHandler<Microsoft.Research.Kinect.Nui.StatusChangedEventArgs>(Kinects_StatusChanged);
            _kinects = new MocKinectDeviceCollection();
            foreach (Microsoft.Research.Kinect.Nui.Runtime runtime in Microsoft.Research.Kinect.Nui.Runtime.Kinects)
            {
                _kinects.Add(CreateMockFromReal(runtime));
            }
            if (_kinects.Count == 0)
                _kinects.Add(new Runtime());
            //_kinects.OnStatusChanged(_kinects, new StatusChangedEventArgs() { Status = KinectStatus.Disconnected, KinectRuntime = _kinects[0] });
        }

        static void Kinects_StatusChanged(object sender, Microsoft.Research.Kinect.Nui.StatusChangedEventArgs e)
        {
            Runtime existingKinectProxy = Kinects.FirstOrDefault(k => k.InstanceIndex == e.KinectRuntime.InstanceIndex);
            if (existingKinectProxy == null)
            {
                existingKinectProxy = CreateMockFromReal(e.KinectRuntime);
                _kinects.Add(existingKinectProxy);
            }
            switch (e.Status)
            {
                case KinectStatus.Connected:

                    if (existingKinectProxy.MockMode == MocKinectMode.Record || existingKinectProxy.MockMode == MocKinectMode.Reality)
                        existingKinectProxy.InitializeRealKinect();
                    break;
                case KinectStatus.Disconnected:
                case KinectStatus.Error:
                case KinectStatus.NotPowered:
                case KinectStatus.NotReady:
                default:
                    existingKinectProxy.RealKinectRuntime = null;
                    break;
            }
            Kinects.OnStatusChanged(sender, new StatusChangedEventArgs() { Status = e.Status, KinectRuntime = existingKinectProxy });
        }

        static Runtime CreateMockFromReal(Microsoft.Research.Kinect.Nui.Runtime real)
        {
            Runtime mock = new Runtime();
            mock.InstanceIndex = real.InstanceIndex;
            mock.InstanceName = real.InstanceName;
            mock.RealKinectRuntime = real;
            return mock;
        }

        #endregion

        Control parentControl;

        #region Kinect Members

        public string InstanceName { get; private set; }
        public int InstanceIndex { get; private set; }

        public event EventHandler<ImageFrameReadyEventArgs> VideoFrameReady;
        public event EventHandler<ImageFrameReadyEventArgs> DepthFrameReady;
        public event EventHandler<SkeletonFrameReadyEventArgs> SkeletonFrameReady;
        private RuntimeOptions runtimeOptions;

        internal void kinectRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            if (VideoFrameReady != null)
                VideoFrameReady(sender, e);
        }

        internal void kinectRuntime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            if (DepthFrameReady != null)
                DepthFrameReady(sender, e);
        }

        internal void kinectRuntime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            if (SkeletonFrameReady != null)
                SkeletonFrameReady(sender, e);
        }

        internal bool HasVideoInvokations()
        {
            EventHandler<ImageFrameReadyEventArgs> eventHandler = Interlocked.CompareExchange<EventHandler<ImageFrameReadyEventArgs>>(ref VideoFrameReady, null, null);
            return eventHandler != null && eventHandler.GetInvocationList().Length != 0;
        }

        internal bool HasDepthInvokations()
        {
            EventHandler<ImageFrameReadyEventArgs> eventHandler = Interlocked.CompareExchange<EventHandler<ImageFrameReadyEventArgs>>(ref DepthFrameReady, null, null);
            return eventHandler != null && eventHandler.GetInvocationList().Length != 0;
        }

        internal bool HasSkeletonInvokations()
        {
            EventHandler<SkeletonFrameReadyEventArgs> eventHandler = Interlocked.CompareExchange<EventHandler<SkeletonFrameReadyEventArgs>>(ref SkeletonFrameReady, null, null);
            return eventHandler != null && eventHandler.GetInvocationList().Length != 0;
        }
        public KinectStatus Status
        {
            get
            {
                if (MockMode == MocKinectMode.Replay) return KinectStatus.Connected;
                else if (RealKinectRuntime == null) return KinectStatus.Disconnected;
                else return RealKinectRuntime.Status;
            }
        }

        public Proxies.CameraProxy NuiCamera { get; private set; }

        public Proxies.ImageStreamProxy DepthStream { get; private set; }

        public Proxies.ImageStreamProxy VideoStream { get; private set; }

        public Proxies.SkeletonEngineProxy SkeletonEngine { get; private set; }
        #endregion

        #region Proxies Kinect members

        public Microsoft.Research.Kinect.Nui.Runtime RealKinectRuntime;

        private MocKinectMode _mockMode = MocKinectMode.Reality;

        public MocKinectMode MockMode
        {
            get { return _mockMode; }
            private set { _mockMode = value; }
        }

        public string RecordFilename { get; set; }

        private string RecordFolder { get; set; }

        private Runtime()
        {
            NuiCamera = new Proxies.CameraProxy(this);
            DepthStream = new Proxies.ImageStreamProxy(this, ImageStreamType.Depth);
            VideoStream = new Proxies.ImageStreamProxy(this, ImageStreamType.Video);
        }

        public void SetMode(MocKinectMode mockMode)
        {
            //parentControl = control;
            //control.BeginInvoke(new MethodInvoker(delegate
            {
                if (MockMode == mockMode) return;

                VideoStream.FlushAndClose();
                DepthStream.FlushAndClose();

                MockMode = mockMode;
                if (MockMode == MocKinectMode.Record || MockMode == MocKinectMode.Replay)
                {
                    if (string.IsNullOrEmpty(RecordFilename) || Control.ModifierKeys == Keys.Control)
                    {
                        //SaveFileDialog sfd = new SaveFileDialog();
                        //sfd.CheckFileExists = true;
                        //sfd.Filter = "*.kinectRecord|*.kinectRecord";
                        //if (MockMode == MocKinectMode.Record)
                        //{
                        //    sfd.FileName = DateTime.Now.ToString("yyyy-MM-dd hh-mm") + ".kinectRecord";
                        //}
                        //if (string.IsNullOrEmpty(sfd.InitialDirectory))
                        //    sfd.InitialDirectory = Path.Combine(Environment.CurrentDirectory);
                        //if (sfd.ShowDialog() == DialogResult.Cancel)
                        //{
                        //    SetMode(MocKinectMode.Reality);
                        //    return;
                        //}
                        RecordFilename = Path.Combine(Environment.CurrentDirectory, "Record.kinectRecord");
                        RecordFolder = RecordFilename.Replace(".kinectRecord", "_files");
                    }
                    if (!Directory.Exists(RecordFolder))
                        Directory.CreateDirectory(RecordFolder);
                    // overwrite old record
                    else if (MockMode == MocKinectMode.Record)
                    {
                        Directory.Delete(RecordFolder, true);
                        Thread.Sleep(300);
                        Directory.CreateDirectory(RecordFolder);
                    }
                    VideoStream.InitPlaybackFolder(RecordFolder);
                    DepthStream.InitPlaybackFolder(RecordFolder);
                }
                //_kinects.OnStatusChanged(_kinects, new StatusChangedEventArgs() { Status = Status, KinectRuntime = this });
            }
            //));
        }

        public void Initialize(RuntimeOptions options)
        {
            runtimeOptions = options;
            if (MockMode == MocKinectMode.Reality || MockMode == MocKinectMode.Record)
            {
                InitializeRealKinect();
            }
        }

        void InitializeRealKinect()
        {
            if (Runtime.Kinects.Count > 0)
            {
                if ((int)runtimeOptions != 0)
                {
                    try
                    {
                        RealKinectRuntime = Microsoft.Research.Kinect.Nui.Runtime.Kinects.FirstOrDefault(k => k.InstanceIndex == this.InstanceIndex);
                        RealKinectRuntime.Initialize(runtimeOptions);
                        DepthStream.OpenRealImageStreamIfNeeded();
                        VideoStream.OpenRealImageStreamIfNeeded();
                    }
                    catch (Exception ex)
                    {
                        // Physical Kinect has not been found.
                        // We don't throw exception because user may just want replay
                    }
                }
            }
        }

        public void Uninitialize()
        {
            if (RealKinectRuntime != null)
            {
                RealKinectRuntime.Uninitialize();
                RealKinectRuntime = null;
            }
        }

        #endregion

    }

}

